package com.starview.star;

import com.starview.platform.data.type.Location;
import com.starview.platform.data.value.ResourceURI;
import com.starview.platform.resource.ResourceException;
import com.starview.platform.resource.Resources;
import com.starview.platform.resource.catalog.Catalog;
import com.starview.platform.resource.catalog.CatalogException;
import com.starview.star.code.repository.CodeRepository;
import com.starview.star.code.repository.RepositoryException;
import com.starview.star.compiler.ErrorReport;
import com.starview.star.compiler.sources.PackageGrapher;
import com.starview.star.compiler.sources.PkgSpec;
import com.starview.star.compiler.util.PrettyPrintDisplay;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Manage the compilation of a star file, together with any files that it depends on.
 * <p/>
 * This is achieved by first of all performing an import-level dependency analysis on the source file.
 *
 * @author fgm
 */
public class StarMake{
  public static ResourceURI compile(CodeRepository repository, ResourceURI srcURI, Catalog catalog, ErrorReport report){
    List<List<PkgSpec>> sorted = PackageGrapher.buildImportGraph(srcURI, repository, catalog).sortDependencies();

    if (StarCompiler.SHOWGRAPH)
      report.reportInfo(showGraph(srcURI, sorted));

    int errCount = report.errorCount();
    Location loc = Location.location(srcURI);
    Set<ResourceURI> toCompile = new HashSet<>();

    // First of all, we check that there are no circularities in the graph
    for (List<PkgSpec> group : sorted)
      if (group.size() > 1)
        report.reportError("these packages have a circular dependency: " + group, loc);

    ResourceURI retURI = null;

    for (List<PkgSpec> group : sorted) {
      for (PkgSpec src : group)
        if (report.noNewErrors(errCount)) {
          try {
            ResourceURI uri = src.getUri();
            String hash = Resources.resourceHash(uri);
            String currHash = repository.findHash(uri);
            if (currHash == null || !currHash.equals(hash) || toCompile.contains(uri)) {
              repository.removeRepositoryNode(uri);
              ResourceURI newURI = CompileDriver.compilePackage(repository, uri, src.getCatalog(), hash, report);
              if (uri == srcURI) {
                retURI = newURI;
              }
              toCompile.addAll(src.getDeps());
              toCompile.remove(uri);
            }
          } catch (CatalogException | RepositoryException | ResourceException e) {
            report.reportError("catalog problem: " + e.getMessage() + "\nin compiling " + src, loc);
          }
        }
    }
    return retURI;
  }

  private static String showGraph(ResourceURI uri, List<List<PkgSpec>> pkgs){
    PrettyPrintDisplay disp = new PrettyPrintDisplay();

    disp.append("Pkg dependency graph for ");
    uri.prettyPrint(disp);
    disp.append("\n");

    String sep = "";
    for (List<PkgSpec> group : pkgs) {
      disp.append(sep);
      sep = "\n";
      disp.prettyPrint(group, "; ");
    }

    return disp.toString();
  }

  /*
   * Set up a repository with the standard code
   */
  public static void setupRepository(CodeRepository repository, ErrorReport errors){
    compile(repository, StarCompiler.starRulesURI, StarRules.starCatalog(), errors);
  }
}
